package com.katesoft.scale4j.web.support;

import com.katesoft.scale4j.web.model.Role;
import com.katesoft.scale4j.web.model.User;
import org.apache.commons.codec.digest.DigestUtils;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;

import static com.katesoft.scale4j.common.services.IBeanNameReferences.HIBERNATE_TEMPLATE;
import static com.katesoft.scale4j.common.services.IBeanNameReferences.TRANSACTION_TEMPLATE;
import static com.katesoft.scale4j.web.model.Role.PROP_ROLE;
import static com.katesoft.scale4j.web.security.ISecurityRoles.ROLE_SYSTEM_ADMIN;
import static java.util.Arrays.asList;
import static org.apache.commons.collections.CollectionUtils.union;
import static org.perf4j.helpers.MiscUtils.splitAndTrim;

/**
 * This class can be used for storing supported {@link Role}.
 *
 * @author kate2007
 */
public class DefaultDataLoaderListener implements ApplicationListener<ContextRefreshedEvent>
{
    private String adminUsername;
    private String adminPassword;
    private Collection<String> roles = new LinkedHashSet<String>();

    @Override
    public void onApplicationEvent(final ContextRefreshedEvent event)
    {
        event.getApplicationContext().getBean(TRANSACTION_TEMPLATE, TransactionTemplate.class).
            execute(new TransactionCallback<Object>()
            {
                @Override
                public Object doInTransaction(TransactionStatus status)
                {
                    event.getApplicationContext().getBean(HIBERNATE_TEMPLATE, HibernateTemplate.class).
                        execute(new HibernateCallback<Object>()
                        {
                            @SuppressWarnings({"unchecked"})
                            @Override
                            public Object doInHibernate(Session session) throws HibernateException, SQLException
                            {
                                Role adminRole = null;
                                Criteria criteria;
                                Collection<String> supportedRoles = union(Role.supportedRoles(), roles);
                                for (final String role : supportedRoles) {
                                    criteria = session.createCriteria(Role.class);
                                    criteria.add(Restrictions.eq(PROP_ROLE, role));
                                    Role persistentRole = (Role) criteria.uniqueResult();
                                    if (persistentRole == null) {
                                        persistentRole = new Role();
                                        persistentRole.setRole(role);
                                        persistentRole.setDescription(String.format("this description was generated by system for role [%s]", role));
                                        session.save(persistentRole);
                                        persistentRole.getUniqueIdentifier();
                                    }
                                    if (persistentRole.getRole().equalsIgnoreCase(ROLE_SYSTEM_ADMIN)) {
                                        adminRole = persistentRole;
                                    }
                                }
                                criteria = session.createCriteria(User.class);
                                criteria.add(Restrictions.eq(User.PROP_LOGIN, adminUsername));
                                User persistentUser = (User) criteria.uniqueResult();
                                if (persistentUser == null) {
                                    persistentUser = new User();
                                    persistentUser.setLogin(adminUsername);
                                    persistentUser.setPassword(DigestUtils.shaHex(adminPassword));
                                    persistentUser.setRoles(Collections.singleton(adminRole));
                                    persistentUser.getAccount();
                                    persistentUser.getPerson();
                                    session.save(persistentUser);
                                    persistentUser.getUniqueIdentifier();
                                }
                                return null;
                            }
                        });
                    return null;
                }
            });
    }

    /**
     * typically there is admin user in application. This method allows to specify admin's username.
     *
     * @param adminUsername login of admin user.
     */
    @Required
    public void setAdminUsername(String adminUsername)
    {
        this.adminUsername = adminUsername;
    }

    /**
     * typically there is admin user in application. This method allows to specify admin's password.
     *
     * @param adminPassword login of admin user.
     */
    @Required
    public void setAdminPassword(String adminPassword)
    {
        this.adminPassword = adminPassword;
    }

    /**
     * inject additional roles to be persisted into database.
     *
     * @param roles comma separated
     */
    public void setRoles(String roles)
    {
        this.roles.addAll(asList(splitAndTrim(roles, ",")));
    }

    /**
     * inject collection of additional roles to be persisted.
     *
     * @param roles collection of roles.
     */
    public void setRoles(Collection<String> roles)
    {
        this.roles = roles;
    }
}
